﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using Vux.Chart.Objects;
using Vux.Chart.Helper;

namespace Vux.Chart
{
    public partial class LineChart : Control
    {
        public LineChart()
        {
            InitializeComponent();
            // control style
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.DoubleBuffer | ControlStyles.UserPaint, true);
            // when dispose
            Disposed += new EventHandler(LineChart_Disposed);
            // initialize members
            m_ht_series = new Hashtable();
            m_pen_black = new Pen(Color.Black);
            m_pen_gainsboro = new Pen(Color.Gainsboro);
            m_brush_white = new SolidBrush(Color.White);
            m_brush_black = new SolidBrush(Color.Black);
            // Coordinator
            m_dbrange_x = new DoubleRange(0, 1);
            m_dbrange_y = new DoubleRange(0, 1);
            m_db_unit_x = (m_dbrange_x.Max - m_dbrange_x.Min) / 10;
            m_db_unit_y = (m_dbrange_x.Max - m_dbrange_x.Min) / 10;
            // Thiết lập vùng vẽ dữ liệu
            m_int_padding_top = 10;
            m_int_padding_right = 100;
            m_int_padding_left = 70;
            m_int_padding_bottom = 100;

            // Thiết lập vùng điều khiển
            MinimumSize = new Size(m_int_padding_left + m_int_padding_right, m_int_padding_top + m_int_padding_bottom);
            m_font_text = new Font("Arial", 8.5F);
        }

        #region Private Members

        private Hashtable m_ht_series;
        private Pen m_pen_black;
        private Pen m_pen_gainsboro;
        private Brush m_brush_white;
        private Brush m_brush_black;

        private DoubleRange m_dbrange_x;
        private DoubleRange m_dbrange_y;
        private double m_db_unit_x;
        private double m_db_unit_y;

        private int m_int_padding_top;
        private int m_int_padding_right;
        private int m_int_padding_left;
        private int m_int_padding_bottom;

        private Font m_font_text;

        #endregion

        #region Properties
        /// <summary>
        /// Y range
        /// </summary>
        public DoubleRange RangeY
        {
            get { return m_dbrange_y; }
            set
            {
                m_dbrange_y = value;
                Invalidate();
            }
        }
        /// <summary>
        /// X range
        /// </summary>
        public DoubleRange RangeX
        {
            get { return m_dbrange_x; }
            set
            {
                m_dbrange_x = value;
                Invalidate();
            }
        }
        /// <summary>
        /// Đơn vị độ chia theo X
        /// Hai thông số UnitX, UnitY phải được thiết đặt trước giá trị: RangeX, RangeY
        /// Thì giá trị này mới có hiệu lực
        /// </summary>
        public double UnitX
        {
            get { return m_db_unit_x; }
            set { m_db_unit_x = value; }
        }
        /// <summary>
        /// Đơn vị độ chia theo Y
        /// - Giả sử y nằm trong khoảng từ [0, 1]
        /// - Muốn chia làm 10 khoảng ta đặt giá trị của UnitY là 0.1
        /// </summary>
        public double UnitY
        {
            get { return m_db_unit_y; }
            set { m_db_unit_y = value; }
        }

        #endregion

        #region Event Handler
        private void LineChart_Disposed(object sender, EventArgs e)
        {
            m_pen_black.Dispose();
            m_brush_white.Dispose();
        }

        #endregion

        #region Private Methods

        private void UpdateYRange()
        {
            var minY = double.MaxValue;
            var maxY = double.MinValue;

            // walk through all data series
            var en = m_ht_series.GetEnumerator(); // Enumerator
            while (en.MoveNext())
            {
                DataSeries series = (DataSeries)en.Value;
                // get data of the series
                double[,] data = series.Data;

                if ((series.UpdateYRange == true) && (data != null))
                {
                    for (int i = 0, n = data.GetLength(0); i < n; i++)
                    {
                        double v = data[i, 1];
                        // check for max
                        if (v > maxY)
                            maxY = v;
                        // check for min
                        if (v < minY)
                            minY = v;
                    }
                }
            }

            // update Y range, if there are any data
            if ((minY != double.MaxValue) || (maxY != double.MinValue))
            {
                m_dbrange_y = new DoubleRange(minY, maxY);
            }
        }
        #endregion

        #region Protected Methods

        protected override void OnPaint(PaintEventArgs pe)
        {
            // base.OnPaint(pe);
            var g = pe.Graphics;
            // độ dài của tỉ lệ mới theo X
            var clientWidth = this.ClientRectangle.Width;   // client width
            // độ dài của tỉ lệ mới theo Y
            var clientHeight = this.ClientRectangle.Height; // client height

            // white background
            g.FillRectangle(m_brush_white, 0, 0, clientWidth - 1, clientHeight - 1);
            // black border
            g.DrawRectangle(m_pen_black, 0, 0, clientWidth - 1, clientHeight - 1);
            if (m_dbrange_x.Length != 0)
            {
                var scaleLengthX = clientWidth - m_int_padding_left - m_int_padding_right;
                var scaleLengthY = clientHeight - m_int_padding_top - m_int_padding_bottom;

                // Cách trái, phải mỗi bên 10 pixel: clientHeight - 20
                var xFactor = scaleLengthX / (float)m_dbrange_x.Length;
                // Cách dưới (bottom) là 20 pixel
                var yFactor = scaleLengthY / (float)((m_dbrange_y.Length != 0) ? m_dbrange_y.Length : 1);

                var count_series = 0;   // Đếm số đồ thị được đặt tên
                var en = m_ht_series.GetEnumerator();
                while (en.MoveNext() == true)
                {
                    var series = en.Value as DataSeries;
                    var data = series.Data; // get data
                    // check data
                    if (data == null)
                        continue;
                    // draw chart
                    var pen = new Pen(series.Color, series.Width);

                    var x1 = (int)((data[0, 0] - m_dbrange_x.Min) * xFactor) + m_int_padding_left;
                    var y1 = (int)((data[0, 1] - m_dbrange_y.Min) * yFactor) + m_int_padding_bottom;

                    // draw
                    var n = data.GetLength(0);
                    for (var i = 1; i < n; i++)
                    {
                        var x2 = (int)((data[i, 0] - m_dbrange_x.Min) * xFactor) + m_int_padding_left;
                        var y2 = (int)((data[i, 1] - m_dbrange_y.Min) * yFactor) + m_int_padding_bottom;

                        // tọa độ ngược đối với Y
                        g.DrawLine(pen, x1, clientHeight - y1, x2, clientHeight - y2);
                        // Đặt lại tọa độ điểm 1
                        x1 = x2;
                        y1 = y2;
                    }
                    // Vẽ tên và màu của các dữ liệu
                    var name = en.Key as string;
                    if (name != null)
                    {
                        // chỉ có series có tên mới tính
                        name = name.Length > 10 ? name.Substring(0, 10) + " ..." : name;
                        var x = m_int_padding_left + scaleLengthX + 5;
                        var y = m_int_padding_top + count_series * 14; // Tên của mỗi đồ thị cách nhau 2 pixel (2 = 14 - 12)
                        var brush = new SolidBrush(series.Color);
                        g.FillEllipse(brush, x, y, 12, 12);
                        g.DrawString(name, m_font_text, m_brush_black, x + 14, y);
                        brush.Dispose();
                        // tăng số đồ thị đếm được
                        count_series++;
                    }
                    pen.Dispose();
                }

                #region Vùng thiết lập các thông số điều khiển
                // Vẽ 2 trục tọa độ
                g.DrawLine(m_pen_gainsboro, m_int_padding_left - 5, m_int_padding_top - 5, m_int_padding_left - 5, m_int_padding_top + scaleLengthY + 5);  // Trục Y
                g.DrawLine(m_pen_gainsboro, m_int_padding_left - 5, m_int_padding_top + scaleLengthY + 5, m_int_padding_left + scaleLengthX + 5, m_int_padding_top + scaleLengthY + 5);    // Trục X
                // Vẽ cột Y
                var offset = 0f;    // offset theo hệ tọa độ.
                var step_y = (float)(m_db_unit_y * yFactor);
                var scaleLength_yheight = scaleLengthY;
                if (step_y < 10) //chỉnh sửa lại bước nhảy step_y
                {
                    step_y = 0.1f * yFactor;
                    if (step_y < 10)
                        step_y = 10;    // min = 10
                }
                while (offset < scaleLength_yheight)
                {
                    // xác định tọa độ chia
                    var x1 = (float)m_int_padding_left - 7; // lùi lại 7-5=2 đơn vị so với trục y để có cái thò ra ngoài :D
                    var y1 = offset + m_int_padding_bottom;
                    var x2 = (float)m_int_padding_left + scaleLengthX;
                    // tọa độ ngược
                    y1 = clientHeight - y1;
                    var y2 = y1;
                    g.DrawLine(m_pen_gainsboro, x1, y1, x2, y2);
                    // vẽ các thông số
                    var step_value = offset / yFactor + m_dbrange_y.Min;  // tính lại giá trị của thang đo theo hệ số yFactor
                    var str_draw = step_value.ToString("0.000");
                    var str_size = g.MeasureString(str_draw, m_font_text);
                    g.DrawString(str_draw, m_font_text, m_brush_black, x1 - 5 - str_size.Width, y1 - (str_size.Height / 2));
                    // tăng giá trị
                    offset += step_y;
                }

                #endregion
            }

        }

        #endregion

        #region Public Methods

        public void UpdateSeries(string ip_str_name, double[,] ip_db_data)
        {
            var v_series_data = m_ht_series[ip_str_name] as DataSeries;
            if (v_series_data == null)
                throw new Exception("Could not found, series: " + ip_str_name);
            v_series_data.Data = ip_db_data;
            // Update Y range
            if (v_series_data.UpdateYRange == true)
            {
                UpdateYRange();
            }
            // Redraw
            Invalidate();
        }

        public void AddSeries(string ip_str_name, Color ip_cl_color, float ip_int_width)
        {
            AddSeries(ip_str_name, ip_cl_color, ip_int_width, true);
        }

        public void AddSeries(string ip_str_name, Color ip_cl_color, float ip_int_width, bool ip_bl_updateYrange)
        {
            // new series
            var v_dt_series = new DataSeries();
            v_dt_series.Color = ip_cl_color;
            v_dt_series.Width = ip_int_width;
            v_dt_series.UpdateYRange = ip_bl_updateYrange;
            // add to table
            m_ht_series.Add(ip_str_name, v_dt_series);
        }

        public void RemoveSeries(string ip_str_name)
        {
            m_ht_series.Remove(ip_str_name);
            // Invalidate the control to redraw
            Invalidate();
        }

        public void RemoveAllSeries()
        {
            m_ht_series.Clear();
            Invalidate();
        }
        #endregion

    }
}
